/*
 ------------------------------------------------------------------------------
 Copyright (C) 2009-2011 Eternal Games.

 This file is part of the Eternal Tech source code.

 The Eternal Tech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Eternal Tech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Eternal Tech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// memory.cpp - Memory manager
//

// TODO:
// - Puddles
// - Integrity checking
// - Console commands


#include "../qcommon/qcommon.h"


static memoryPool_t		*m_poolsHashTable[POOLS_HASH_SIZE];
static memoryPool_t		m_pools[MAX_POOLS];
static int				m_numPools;


/*
 ==============================================================================

    PUDDLE MANAGEMENT

 ==============================================================================
*/


void Mem_CreatePuddles ()
{
}


/*
 ==============================================================================

    POOL REGISTRATION AND CREATION

 ==============================================================================
*/


/*
 ==================
 Mem_LoadPool
 ==================
*/
static memoryPool_t *Mem_LoadPool (const char *name)
{
	memoryPool_t   *pool;
	uint		   hashKey;

	// Allocate a new pool
	if (m_numPools == MAX_POOLS)
		Com_Error(ERR_DROP, "Mem_LoadPool: MAX_POOLS hit");

	pool = &m_pools[m_numPools++];

	// Fill it in
	Str_Copy(pool->name, name, sizeof(pool->name));
	pool->numBlocks = 0;
	pool->byteCount = 0;
	pool->blockHeadNode.prev = &pool->blockHeadNode;
	pool->blockHeadNode.next = &pool->blockHeadNode;

	// Add to hash table
	hashKey = Str_HashKey(pool->name, POOLS_HASH_SIZE, false);

	pool->nextHash = m_poolsHashTable[hashKey];
	m_poolsHashTable[hashKey] = pool;

	return pool;
}

/*
 ==================
 Mem_FindPool
 ==================
*/
memoryPool_t *Mem_FindPool (const char *name)
{
	memoryPool_t   *pool;
	uint		   hashKey;

	if (!name || !name[0])
		Com_Error(ERR_DROP, "Mem_FindPool: NULL pool name");

	if (Str_Length(name) >= MAX_PATH_LENGTH)
		Com_Error(ERR_DROP, "Mem_FindPool: pool name exceeds MAX_PATH_LENGTH");

	// Check if it already exists
	hashKey = Str_HashKey(name, POOLS_HASH_SIZE, false);

	for (pool = m_poolsHashTable[hashKey]; pool; pool = pool->nextHash)
	{
		if (!Str_ICompare(pool->name, name))
			return pool;
	}

	// Load it in
	pool = Mem_LoadPool(name);

	if (!pool)
		Com_Error(ERR_DROP, "Mem_FindPool: NULL pool");

	return pool;
}

/*
 ==================
 Mem_RegisterPool
 ==================
*/
memoryPool_t *Mem_RegisterPool (const char *name)
{
	return Mem_FindPool(name);
}


/*
 ==============================================================================

    ALLOCATION AND FREEING

 ==============================================================================
*/


/*
 ==================
 Mem_Alloc
 ==================
*/
void *Mem_Alloc (int size, memoryPool_t *pool)
{
	memoryBlock_t   *block;
	int				realSize;

	if (size < 0)
		Com_Error(ERR_FATAL, "Mem_Alloc: size < 0");

	if (!pool)
		pool = com_genericMemoryPool;
		//Com_Error(ERR_FATAL, "Mem_Alloc: NULL pool");

	// Try to alloc in a puddle first
	block = NULL;

	// Allocate a block of the given size
	if (!block)
	{
		realSize = (size + sizeof(memoryBlock_t) + sizeof(byte) + 31) & ~31;

		block = (memoryBlock_t*)calloc(1, realSize);
		if (!block)
			Com_Error(ERR_FATAL, "Mem_Alloc: failed on allocation of %i bytes", size);

		// Fill it in
		block->pointer = (void *)((byte *)block + sizeof(memoryBlock_t));
		block->size = size;
		block->realSize = realSize;
	}

	block->pool = pool;

	// Link it in to the appropriate pool
	block->prev = &pool->blockHeadNode;
	block->next = pool->blockHeadNode.next;
	block->next->prev = block;
	block->prev->next = block;

	// Increment counters
	pool->numBlocks++;
	pool->byteCount += block->realSize;

	return block->pointer;
}

/*
 ==================
 Mem_Free
 ==================
*/
void Mem_Free (const void *ptr)
{
	memoryBlock_t   *block;

	if (!ptr)
		Com_Error(ERR_FATAL, "Mem_Free: NULL pointer");

	// Get the block
	block = (memoryBlock_t *)((byte *)ptr - sizeof(memoryBlock_t));

	// Decrement counters
	block->pool->numBlocks--;
	block->pool->byteCount -= block->realSize;

	// Unlink
	block->next->prev = block->prev;
	block->prev->next = block->next;

	// Free it
	free(block);
}

/*
 ==================
 Mem_FreePool

 TODO: Crashes the game module, probably because not all pools are
 freed on a restart
 ==================
*/
void Mem_FreePool (memoryPool_t *pool)
{
#if 0
	memoryBlock_t   *block, *next;
	memoryBlock_t   *headNode = &pool->blockHeadNode;

	if (!pool)
		Com_Error(ERR_FATAL, "Mem_FreePool: NULL pool");

	for (block = pool->blockHeadNode.prev; block != headNode; block = next)
	{
		next = block->prev;

		Mem_Free(block->pointer);
	}
#endif
}


/*
 ==============================================================================

    UTILITIES

 ==============================================================================
*/


/*
 ==================
 Mem_DupString
 ==================
*/
char *Mem_DupString (const char *string, memoryPool_t *pool)
{
	char    *buffer;
	int		length;

	if (!string)
		Com_Error(true, "Mem_DupString: NULL string");

	length = Str_Length(string) + 1;

	buffer = (char *)Mem_Alloc(length, pool);
	Str_Copy(buffer, string, length);

	return buffer;
}

/*
 ==================
 Mem_DupData
 ==================
*/
void *Mem_DupData (const void *data, int size, memoryPool_t *pool)
{
	void	*buffer;

	if (!data)
		Com_Error(ERR_FATAL, "Mem_DupData: NULL data");

	buffer = (void *)Mem_Alloc(size, pool);
	memcpy(buffer, data, size);

	return buffer;
}


/*
 ==============================================================================

    CONSOLE COMMANDS

 ==============================================================================
*/


void Mem_Stats_f ()
{
	memoryPool_t   *pool;
	int			   i;

	Com_Printf("\n");
	//Com_Printf("      -stg -amb -ops -reg src -type-- -name--------\n");

	for (i = 0; i < m_numPools; i++)
	{
		pool = &m_pools[i];

		Com_Printf("%4i ", i);

		Com_Printf("%6i ", pool->numBlocks);

		Com_Printf("%9iB ", pool->byteCount);

		Com_Printf("(%6.3fMB) ", pool->byteCount/1048576.0f);

		Com_Printf("%s\n", pool->name);
	}

	Com_Printf("--------------------------\n");
	Com_Printf("%i total fonts\n", m_numPools);
	Com_Printf("\n");
}

void Mem_Dump_f ()
{
}


/*
 ==============================================================================

    INITIALIZATION AND SHUTDOWN

 ==============================================================================
*/


/*
 ==================
 Mem_Init
 ==================
*/
void Mem_Init ()
{
	// Add commands
	Cmd_AddCommand("memStats", Mem_Stats_f);
	Cmd_AddCommand("memDump", Mem_Dump_f);

	// Create puddle tables
	Mem_CreatePuddles();

	Com_Printf("Memory Manager Initialized\n");
}

/*
 ==================
 Mem_Shutdown
 ==================
*/
void Mem_Shutdown ()
{
	// Remove commands
	Cmd_RemoveCommand("memStats");
	Cmd_RemoveCommand("memDump");

	// Clear pool list
	memset(m_poolsHashTable, 0, sizeof(m_poolsHashTable));
	memset(m_pools, 0, sizeof(m_pools));

	m_numPools = 0;
}